﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;

namespace ALDIOrganizer
{
    // A super-class which represents all rotas in the CurrentFile, as well as the _staff
    [Serializable]
    public partial class DataFile : IModifiable
    {
        internal string FileName;
        internal string FileNameNoPath
        {
            get
            {
                return Path.GetFileName(FileName);
            }
        }

        public bool TestForIntegrity(bool pFix)
        {
            
            var _corrupted = false;
            var _fixed = false;
                        
            foreach (Rota _rota in rotas.Values)
            {
                _rota.Employees.Refresh();
                var _ids = (from _employee in _rota.Employees.getWorkers
                            select _employee.Employee.ID).ToArray();
                
                var _new_portion_of_missing_ones = (from _tag in _rota.GetAllTags
                                                    where !_ids.Contains(_tag.Int64Value)
                                                    select _tag.Int64Value).ToArray();
                if (_new_portion_of_missing_ones.Length > 0)
                {   
                    _corrupted = true;
                }
                if (pFix)
                {
                    
                    foreach (var _id in _new_portion_of_missing_ones)
                    {
                        _rota.RemoveRow(_id);
                        _rota.RecomputeTotalHoursField();
                        _fixed = true;
                        
                    }
                }

                var _cells = from _cell in _rota.GetAllCells
                             where ((_cell.MyCellType == CellType.Holiday)
                             | (_cell.HasAnyRequests))
                             select _cell;
                
                foreach (var _cell in _cells)
                {
                        
                        if (_cell.GetStatusID == null) // shouldn't happen
                        {                            
                            _cell.Update();                            
                        }
                        foreach (var _id in _cell.GetStatusID)
                        {
                            

                            var _id_found = true;
                            if (_cell.HasAnyRequests)
                            {
                                _id_found = (SQLiteAccess.GetRequest(_id) != null);
                            }
                            else
                            {
                                if (_cell.MyCellType == CellType.Holiday)
                                {
                                    _id_found = (SQLiteAccess.GetHolidays(_id) != null);
                                }
                            }                   
                            if (!_id_found)
                            {
                                
                                _corrupted = true;
                                if (pFix)
                                {                                    
                                    _cell.Update();
                                    _fixed = true;
                                    
                                    // Debugger.Break();
                                }
                                break;
                            }
                        }
                    }
                }
            if (_fixed)
            {
                var _foo = this.SaveTo(File.OpenWrite(this.FileName));
            }
            return _corrupted;            
        }
        

        public delegate void OnCollectionModifiedEventHandler();
        public event OnCollectionModifiedEventHandler OnRotasCollectionModified;
        public event OnCollectionModifiedEventHandler OnTemplatesCollectionModified;
        // private List<tRota> templates; all the templates we have created

        /* All the rotas, sorted from query to latest.
         * 
         * The DateTime key points to date of each GetParent. */
        private SortedList<DateTime, Rota> _rotas;
        public SortedList<DateTime, Rota> rotas
        {
            get { return _rotas; }
        }
        public Dictionary<string, Template> _templates;
        public Dictionary<string, Template> templates
        {
            get { return _templates; }
        }
        public bool AnyChoiceOfTemplates
        {
            get
            {
                return (templates.Count > 1);
            }
        }

        public BindingList<String> TemplatesDataSource
        {
            get
            {
                var _source = new BindingList<String> {Template.DefaultName};
                foreach(string _template in 
                    (from _queried_template in templates.Keys
                     where _queried_template != Template.DefaultName
                     orderby _queried_template
                     select _queried_template))
                     _source.Add(_template);
                return _source;                
            }
        
        }


        

        public Template GetTemplate(Rota pRefillRota)
        {
            var _name = pRefillRota.TemplateName;
            if (!templates.Keys.Contains(_name))
            {
                return null;
            }
            return templates[_name];
        }

        public IEnumerable<Rota> GetAllRotasWithin(DateTime? start, DateTime? end)
        {
            var query = from rota in rotas.AsEnumerable()
                        select rota;
            // narrowing down...
            if (start.HasValue)
                query = from rota in query
                        where rota.Key >= start.Value.MondayOfThatWeek()
                        select rota;
            // narrowing down...
            if (end.HasValue)
                query = from rota in query
                        where rota.Key <= end.Value.MondayOfThatWeek().AddDays(6)
                        select rota;
            return (from rota in query select rota.Value);                        
        }

        public bool OverOneYear
        {
            get
            {
                return (this.Start.Year != this.End.Year);
            }
        }
        
        public bool AnyRotasActivated
        {
            get
            {
                return (from _rota in rotas.Values
                        where !_rota.IsEmpty
                        select _rota).Any();
            }
        }

        internal void SetParentsAndWorkingHours()
        {
            if (_rotas == null)
            {
                return;
            }
            foreach (var _activated in
                (from _rota in rotas.Values
                 where (!_rota.IsEmpty)
                 select _rota).AsEnumerable())
            {
                _activated.SetParent(_activated);
                _activated.AssignTemplate(GetTemplate(_activated));
                _activated.RecomputeTotalHoursField();                
            }
            
        }

        /// <summary>
        /// 
        /// </summary>
        /// <remarks>
        /// Not safe: will throw an pException if no such records exist.
        /// Make sure 
        /// </remarks>
        public DateTime FirstActivated
        {
            get
            {
                return (from _rota in rotas.Values
                        where !_rota.IsEmpty
                        select _rota).First().Monday;                
            }
        }

        public int EmptiesBefore
        {
            get
            {
                if (rotas.Count == 0) return 0;
                var _result = 0;
                foreach (var _rota in rotas.Values)
                    if (_rota.IsEmpty) _result++;
                    else break;
                return _result;                
            }
        }

        public int EmptiesAfter
        {
            get
            {
                if (rotas.Count == 0) return 0;
                
                var _result = 0;
                var _reversed = rotas.Values.Reverse();
                foreach (var _rota in _reversed)
                    if (_rota.IsEmpty) _result++;
                    else break;
                return _result;
            }
        }

        void Pad(int pBefore, int pAfter)
        {
            if (rotas.Count == 0) return;
            var earliestDate = rotas.Keys.First();
            var lastDate = rotas.Keys.Last();

            var insertDate = earliestDate.AddDays(-7);
            
            for (var i = 0; i < pBefore; i++)
            {
                _rotas.Add(insertDate,
                    Rota.GetEmpty(insertDate));
                insertDate = insertDate.AddDays(-7);
            }

            insertDate = lastDate.AddDays(7);
            for (var i = 0; i < pAfter; i++)
            {
                _rotas.Add(
                    insertDate,
                    Rota.GetEmpty(insertDate));
                _rotas[insertDate].RecomputeWhoOnHolidaysField();
                insertDate = insertDate.AddDays(7);
            }
            if (OnRotasCollectionModified != null)
                OnRotasCollectionModified();
        }

        public void Pad()
        {
            var desiredPaddingBefore = Settings.Default.CALENDAR_PaddingBackwards;
            var desiredPaddingAfter = Settings.Default.CALENDAR_PaddingAhead;

            if (AnyRotasActivated)
            {
                var currentPaddingBefore = EmptiesBefore;
                var currentPaddingAfter = EmptiesAfter;

                var requiredPaddingBefore = desiredPaddingBefore - currentPaddingBefore;
                var requiredPaddingAfter = desiredPaddingAfter - currentPaddingAfter;

                if (requiredPaddingAfter < 0)
                    requiredPaddingAfter = 0;
                if (requiredPaddingBefore < 0)
                    requiredPaddingBefore = 0;

                if (requiredPaddingBefore + requiredPaddingAfter == 0)
                {
                    return; // CurrentFile is padded properly
                }

                Pad(
                    requiredPaddingBefore,
                    requiredPaddingAfter);
            }
            else // there's ONLY empties in the CurrentFile
            {
                var existingEmpties = rotas.Count;
                Pad(
                    0,
                    (desiredPaddingBefore + desiredPaddingAfter) - existingEmpties);
            }
        }

        public void ComputeHolidays(Int64 pWhose)
        {
            foreach (var rota in rotas.Values)
            {
                if (pWhose < 0)
                {
                    // removing collectively
                    rota.ClearHolidayLists();
                }
                foreach (var id in SQLiteAccess.GetEmployeeIDs(pWhose))
                {
                    var holidaysBinary = SQLiteAccess.GetHolidaysForWeek(
                        id,
                        rota.Monday,
                        false).Key.ToByte() >> 1; // >> 1, because the last bit is for the "eigth Day"
                    // - or the "summary" one, indicating whether ANY of the previous 7 are lit up

                    // removing selectively
                    if (pWhose >= 0)
                    {
                        rota.RemoveFromHolidayLists(id);
                    }

                    // "clever coding", but at least it'pShift very concise
                    if (holidaysBinary == 0)
                    {
                        continue;
                    }
                    if (holidaysBinary == 127)
                    {
                        rota.OnHolidaysALLWEEK.Add(id);
                        continue;
                    }
                    // 1, 3, 7, 15... in binary, 0000001, 0000011, 0000111 etc.
                    if (new[] { 1, 3, 7, 15, 31, 63 }.Contains(holidaysBinary))
                    {
                        rota.OnHolidaysLEAVING.Add(id);
                        continue;
                    }
                    // again: 1111110, 1111100, 1111000 etc.
                    if (new[] { 126, 124, 120, 112, 96, 64 }.Contains(holidaysBinary))
                    {
                        rota.OnHolidaysRETURNING.Add(id);
                        continue;
                    }
                    
                    // some other, irregular configuration, such as 0011100 or 1010101
                    rota.OnHolidaysPARTLY.Add(id);
                }
            }            
            Modified = true;
        }

        public void ActivateAll()
        {
            foreach (var rota in rotas.Values)
            {
                rota.Activate();
            }
        }


        public DateTime Start
        {
            get { return this._rotas.First().Key.Date; }
        }
        public DateTime End
        { // It returns Sunday - that'pShift why we still AddAnotherRowString 6 bits
          get { return _rotas.Last().Key.AddDays(6).Date; }
        }

        public DataFile()
        {
            _rotas = new SortedList<DateTime, Rota>();
            _templates = new Dictionary<string, Template>();
        }

        public DataFile(DateTime pFirstMonday)
            : this()
        {
            _rotas.Add(
                pFirstMonday,
                Rota.GetEmpty(pFirstMonday));
            Pad();
        }
                
        
        
        public Template GetTemplateCopy(string templateName)
        {
            if (templates.Keys.Contains(templateName))
                return _templates[templateName].DeepCopy();
            Debugger.Break();
            return null;
        }

        public Rota ActivateEmptyAndPass(DateTime pMonday, string templateName)
        {
            if (!((rotas.Keys.Contains(pMonday))
                  & rotas[pMonday].IsEmpty))
            {
                // Debugger.Break();
                return null;
            }
            var toBeActivated = rotas[pMonday];

            /* Note that it is the ORIGINAL which gets "activated" (Empty switched to false, user'pShift chosen template assigned etc.)
                 * We will pass a deep Copy for editing (so our user will be able to discard any changes), but the activation itself
                 * is permanent and it happens straight away.
                 * Otherwise unpleasant phenomenons would occur - user "creating" (activating) a rota,
                 * returning to the Calendar tab only to discover that it'pShift still empty and he can "create it" again
                 * (because the calendar datagridview is "looking" at the original Copy, which remains unactivated */
            toBeActivated.ActivateEmpty(GetTemplateCopy(templateName));

            if (OnRotasCollectionModified != null)
            {
                OnRotasCollectionModified();
            }
            Pad(); // we have to re-pad now

            Modified = true;

            return toBeActivated.DeepCopy(); // as explained above, deep copying is delayed and happens here
        }


        public void ReuniteWithDatabase(Sheet pRefill)
        {
            if (pRefill == null)
            {
                return;
            }
            if (pRefill.IsRota)
            {
                var asRota = pRefill as Rota;
                var monday = asRota.Monday;
                if (!_rotas.Keys.Contains(asRota.Monday))
                {
                    // Debugger.Break(); // throw exception?
                    return;
                }
                _rotas[monday] = asRota.DeepCopy();
                _rotas[monday].SetParent(_rotas[monday]);
                _rotas[monday].AssignTemplate(GetTemplate(_rotas[monday]));
                _rotas[monday].RecomputeTotalHoursField();
                _rotas[monday].RecomputeWhoOnHolidaysField();
                if (OnRotasCollectionModified != null)
                {
                    OnRotasCollectionModified();
                }                                    
            }
            if (pRefill.IsTemplate)
            {
                var asTemplate = pRefill as Template;
                if (_templates.Keys.Contains(asTemplate.Name))
                {
                    _templates[asTemplate.Name] = asTemplate.DeepCopy();
                }
                else
                {
                    _templates.Add(
                        asTemplate.Name,
                        asTemplate.DeepCopy());
                    if (OnTemplatesCollectionModified != null)
                    {
                        OnTemplatesCollectionModified();
                    }                    
                }
            }            
            Modified = true;            
        }

        public void DeleteTemplate(string pName)
        {
            if(!templates.Keys.Contains(pName))
            {
                // Debugger.Break();
                return;
            }
            if (pName == Template.DefaultName)
            {
                return;
            }

            foreach (var _rota in this.GetRotasBasedOn(pName))
            {
                _rota.SetTemplate(Template.DefaultName,
                    templates[Template.DefaultName].WorkingHours);
            }
            RecalculateRotasBasedOn(Template.DefaultName);
            templates.Remove(pName);
            
            OnTemplatesCollectionModified();
            OnRotasCollectionModified();
            Modified = true;
        }

        public TemplateNameValidationError ValidateTemplateName(string pProposedName)
        {
            /* case insensitive. if templates contain template "Normal",
             * "normal" will be rejected */
            var taken = (from name in templates.Keys
                         select name.ToLower()).
                         ToArray().
                         Contains(
                         pProposedName.ToLower());
            if (taken)
            {
                return TemplateNameValidationError.Taken;
            }
            if (!Template.ValidateName(pProposedName))
            {
                return TemplateNameValidationError.Invalid;
            }
            return TemplateNameValidationError.None;
        }

        public string GetFirstAvailableAlternative(string pProposedName)
        {
            byte offset = 1;
            while (offset <= 100)
            {
                var alternative = Template.SuggestAlternativeName(
                    pProposedName,
                    offset++);
                if (ValidateTemplateName(alternative) ==
                    TemplateNameValidationError.None)
                {
                    return alternative;
                }
            }   
            return String.Empty;
        }

        #region IModifiable Members

        bool _Modified;
        public bool Modified
        {
            get
            {
                return _Modified;
            }
            set
            {
                if (value == _Modified)
                {
                    return;
                }
                _Modified = value;
                if (OnModifiedChanged != null)
                {
                    OnModifiedChanged(value);
                }
            }
        }

        public event Sheet.OnModifiedChangedEventHandler OnModifiedChanged;
        public event Sheet.OnLeavingModifiedEntityEventHandler OnLeavingModifiedEntity;

        public bool OKLeaving()
        {
            if (Modified == false)
            {
                return true;
            }
            if (OnLeavingModifiedEntity != null)
            {
                return OnLeavingModifiedEntity();
            }
            return true;            
        }

        #endregion
    }   		

    



    
}
